"""
统一密码处理系统
================

为所有微服务提供统一的密码哈希、验证、强度检查和安全管理功能。
支持多种哈希算法、安全的密码存储和验证机制。

特性：
- 统一的密码哈希算法（Argon2）
- 密码强度验证
- 安全的密码比较
- 密码历史管理
- 密码重置令牌
- 可配置的安全策略
"""

from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List, Tuple
import logging
import redis.asyncio as redis

from passlib.context import CryptContext
from passlib.hash import argon2
import hashlib
import secrets
import string

from ...common.config import get_service_config
logger = logging.getLogger(__name__)


class UnifiedPasswordManager:
    """统一密码管理器"""
    
    def __init__(self, service_name: str = "default"):
        """
        初始化密码管理器
        
        Args:
            service_name: 服务名称，用于获取特定配置
        """
        self.service_name = service_name
        self.config = get_service_config(service_name)
        self.password_config = self.config.get("password", {})
        
        # 密码策略配置
        self.min_length = self.password_config.get("min_length", 8)
        self.max_length = self.password_config.get("max_length", 128)
        self.require_uppercase = self.password_config.get("require_uppercase", True)
        self.require_lowercase = self.password_config.get("require_lowercase", True)
        self.require_digits = self.password_config.get("require_digits", True)
        self.require_special = self.password_config.get("require_special", True)
        self.password_history_count = self.password_config.get("history_count", 5)
        self.reset_token_expire_hours = self.password_config.get("reset_token_expire_hours", 24)
        
        # 创建密码上下文（使用Argon2作为主要算法）
        self.pwd_context = CryptContext(
            schemes=["argon2", "bcrypt"],
            default="argon2",
            argon2__memory_cost=65536,  # 64MB
            argon2__time_cost=3,        # 3 iterations
            argon2__parallelism=2,      # 2 parallel threads
            deprecated="auto"
        )
        
        # 特殊字符集合
        self.special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?"
        
        # 数据库管理器
        self._db_manager = None
    
    @property
    def db_manager(self):
        """获取数据库管理器"""
        if self._db_manager is None:
            # 延迟导入避免循环依赖
            from ...data.database.compatibility.managers_adapter import get_postgresql_manager
            self._db_manager = get_postgresql_manager()
        return self._db_manager
    
    def hash_password(self, password: str) -> str:
        """
        哈希密码
        
        Args:
            password: 明文密码
            
        Returns:
            str: 哈希后的密码
        """
        try:
            if not password:
                raise ValueError("Password cannot be empty")
            
            # 验证密码强度
            validation_result = self.validate_password_strength(password)
            if not validation_result["is_valid"]:
                raise ValueError(f"Password validation failed: {validation_result['errors']}")
            
            # 使用Argon2哈希密码
            hashed = self.pwd_context.hash(password)
            
            logger.info("Password hashed successfully")
            return hashed
            
        except Exception as e:
            logger.error(f"Password hashing failed: {e}")
            raise
    
    def verify_password(self, plain_password: str, hashed_password: str) -> bool:
        """
        验证密码
        
        Args:
            plain_password: 明文密码
            hashed_password: 哈希后的密码
            
        Returns:
            bool: 密码是否匹配
        """
        try:
            if not plain_password or not hashed_password:
                return False
            
            # 使用passlib进行安全比较
            is_valid = self.pwd_context.verify(plain_password, hashed_password)
            
            # 检查是否需要重新哈希（算法升级）
            if is_valid and self.pwd_context.needs_update(hashed_password):
                logger.info("Password hash needs update")
                # 这里可以触发密码重新哈希的逻辑
            
            return is_valid
            
        except Exception as e:
            logger.error(f"Password verification failed: {e}")
            return False
    
    def validate_password_strength(self, password: str) -> Dict[str, Any]:
        """
        验证密码强度
        
        Args:
            password: 密码
            
        Returns:
            Dict[str, Any]: 验证结果
        """
        errors = []
        score = 0
        
        if not password:
            return {
                "is_valid": False,
                "score": 0,
                "strength": "invalid",
                "errors": ["Password is required"]
            }
        
        # 长度检查
        if len(password) < self.min_length:
            errors.append(f"Password must be at least {self.min_length} characters long")
        elif len(password) >= self.min_length:
            score += 1
        
        if len(password) > self.max_length:
            errors.append(f"Password cannot exceed {self.max_length} characters")
        
        # 大写字母检查
        if self.require_uppercase:
            if any(c.isupper() for c in password):
                score += 1
            else:
                errors.append("Password must contain at least one uppercase letter")
        
        # 小写字母检查
        if self.require_lowercase:
            if any(c.islower() for c in password):
                score += 1
            else:
                errors.append("Password must contain at least one lowercase letter")
        
        # 数字检查
        if self.require_digits:
            if any(c.isdigit() for c in password):
                score += 1
            else:
                errors.append("Password must contain at least one digit")
        
        # 特殊字符检查
        if self.require_special:
            if any(c in self.special_chars for c in password):
                score += 1
            else:
                errors.append("Password must contain at least one special character")
        
        # 额外强度检查
        if len(password) >= 12:
            score += 1
        
        if len(set(password)) >= len(password) * 0.7:  # 字符多样性
            score += 1
        
        # 常见密码检查
        if self._is_common_password(password):
            errors.append("Password is too common")
            score -= 2
        
        # 计算强度等级
        if score <= 2:
            strength = "weak"
        elif score <= 4:
            strength = "medium"
        elif score <= 6:
            strength = "strong"
        else:
            strength = "very_strong"
        
        return {
            "is_valid": len(errors) == 0,
            "score": max(0, score),
            "strength": strength,
            "errors": errors
        }
    
    def _is_common_password(self, password: str) -> bool:
        """检查是否为常见密码"""
        # 简化的常见密码列表
        common_passwords = {
            "password", "123456", "123456789", "qwerty", "abc123",
            "password123", "admin", "letmein", "welcome", "monkey",
            "dragon", "master", "shadow", "qwertyuiop", "123123"
        }
        
        return password.lower() in common_passwords
    
    def generate_password(self, length: int = 12, include_symbols: bool = True) -> str:
        """
        生成安全密码
        
        Args:
            length: 密码长度
            include_symbols: 是否包含特殊字符
            
        Returns:
            str: 生成的密码
        """
        if length < self.min_length:
            length = self.min_length
        
        # 构建字符集
        chars = string.ascii_letters + string.digits
        if include_symbols:
            chars += self.special_chars
        
        # 确保密码包含各种类型的字符
        password = []
        
        # 至少包含一个小写字母
        password.append(secrets.choice(string.ascii_lowercase))
        
        # 至少包含一个大写字母
        password.append(secrets.choice(string.ascii_uppercase))
        
        # 至少包含一个数字
        password.append(secrets.choice(string.digits))
        
        # 如果需要，至少包含一个特殊字符
        if include_symbols:
            password.append(secrets.choice(self.special_chars))
        
        # 填充剩余长度
        for _ in range(length - len(password)):
            password.append(secrets.choice(chars))
        
        # 随机打乱密码
        secrets.SystemRandom().shuffle(password)
        
        return ''.join(password)
    
    async def store_password_history(self, user_id: str, hashed_password: str):
        """
        存储密码历史
        
        Args:
            user_id: 用户ID
            hashed_password: 哈希后的密码
        """
        try:
            redis_client = self.db_manager.get_redis("cache")
            history_key = f"password_history:{user_id}"
            
            # 获取当前历史
            history = await redis_client.lrange(history_key, 0, -1)
            
            # 添加新密码到历史
            await redis_client.lpush(history_key, hashed_password)
            
            # 保持历史记录数量限制
            await redis_client.ltrim(history_key, 0, self.password_history_count - 1)
            
            # 设置过期时间（1年）
            await redis_client.expire(history_key, 365 * 24 * 3600)
            
        except Exception as e:
            logger.error(f"Failed to store password history: {e}")
    
    async def check_password_history(self, user_id: str, new_password: str) -> bool:
        """
        检查密码是否在历史中使用过
        
        Args:
            user_id: 用户ID
            new_password: 新密码
            
        Returns:
            bool: 是否在历史中使用过
        """
        try:
            redis_client = self.db_manager.get_redis("cache")
            history_key = f"password_history:{user_id}"
            
            # 获取密码历史
            history = await redis_client.lrange(history_key, 0, -1)
            
            # 检查新密码是否与历史密码匹配
            for hashed_password in history:
                if self.verify_password(new_password, hashed_password.decode()):
                    return True
            
            return False
            
        except Exception as e:
            logger.error(f"Failed to check password history: {e}")
            return False
    
    def generate_reset_token(self) -> str:
        """
        生成密码重置令牌
        
        Returns:
            str: 重置令牌
        """
        return secrets.token_urlsafe(32)
    
    async def store_reset_token(self, user_id: str, token: str) -> bool:
        """
        存储密码重置令牌
        
        Args:
            user_id: 用户ID
            token: 重置令牌
            
        Returns:
            bool: 是否存储成功
        """
        try:
            redis_client = self.db_manager.get_redis("cache")
            token_key = f"reset_token:{token}"
            
            # 存储令牌信息
            token_data = {
                "user_id": user_id,
                "created_at": datetime.utcnow().isoformat(),
                "expires_at": (datetime.utcnow() + timedelta(hours=self.reset_token_expire_hours)).isoformat()
            }
            
            # 设置令牌过期时间
            await redis_client.setex(
                token_key,
                self.reset_token_expire_hours * 3600,
                str(token_data)
            )
            
            return True
            
        except Exception as e:
            logger.error(f"Failed to store reset token: {e}")
            return False
    
    async def verify_reset_token(self, token: str) -> Optional[str]:
        """
        验证密码重置令牌
        
        Args:
            token: 重置令牌
            
        Returns:
            Optional[str]: 用户ID（如果令牌有效）
        """
        try:
            redis_client = self.db_manager.get_redis("cache")
            token_key = f"reset_token:{token}"
            
            # 获取令牌数据
            token_data = await redis_client.get(token_key)
            if not token_data:
                return None
            
            # 解析令牌数据
            import ast
            data = ast.literal_eval(token_data.decode())
            
            # 检查过期时间
            expires_at = datetime.fromisoformat(data["expires_at"])
            if datetime.utcnow() > expires_at:
                await redis_client.delete(token_key)
                return None
            
            return data["user_id"]
            
        except Exception as e:
            logger.error(f"Failed to verify reset token: {e}")
            return None
    
    async def invalidate_reset_token(self, token: str):
        """
        使重置令牌失效
        
        Args:
            token: 重置令牌
        """
        try:
            redis_client = self.db_manager.get_redis("cache")
            token_key = f"reset_token:{token}"
            await redis_client.delete(token_key)
        except Exception as e:
            logger.error(f"Failed to invalidate reset token: {e}")
    
    def hash_sensitive_data(self, data: str, salt: Optional[str] = None) -> Tuple[str, str]:
        """
        哈希敏感数据（如身份证号、手机号等）
        
        Args:
            data: 敏感数据
            salt: 盐值（可选）
            
        Returns:
            Tuple[str, str]: (哈希值, 盐值)
        """
        if not salt:
            salt = secrets.token_hex(16)
        
        # 使用SHA-256 + 盐值进行哈希
        hasher = hashlib.sha256()
        hasher.update((data + salt).encode('utf-8'))
        hashed = hasher.hexdigest()
        
        return hashed, salt
    
    def verify_sensitive_data(self, data: str, hashed_data: str, salt: str) -> bool:
        """
        验证敏感数据
        
        Args:
            data: 原始数据
            hashed_data: 哈希后的数据
            salt: 盐值
            
        Returns:
            bool: 是否匹配
        """
        try:
            hasher = hashlib.sha256()
            hasher.update((data + salt).encode('utf-8'))
            return hasher.hexdigest() == hashed_data
        except Exception:
            return False


# =============================================================================
# 全局密码管理器实例
# =============================================================================

_password_managers = {}


def get_password_manager(service_name: str = "default") -> UnifiedPasswordManager:
    """
    获取密码管理器实例
    
    Args:
        service_name: 服务名称
        
    Returns:
        UnifiedPasswordManager: 密码管理器实例
    """
    if service_name not in _password_managers:
        _password_managers[service_name] = UnifiedPasswordManager(service_name)
    return _password_managers[service_name]


# =============================================================================
# 便捷函数
# =============================================================================

def hash_password(password: str, service_name: str = "default") -> str:
    """
    哈希密码（便捷函数）
    
    Args:
        password: 明文密码
        service_name: 服务名称
        
    Returns:
        str: 哈希后的密码
    """
    manager = get_password_manager(service_name)
    return manager.hash_password(password)


def verify_password(plain_password: str, hashed_password: str, service_name: str = "default") -> bool:
    """
    验证密码（便捷函数）
    
    Args:
        plain_password: 明文密码
        hashed_password: 哈希后的密码
        service_name: 服务名称
        
    Returns:
        bool: 密码是否匹配
    """
    manager = get_password_manager(service_name)
    return manager.verify_password(plain_password, hashed_password)


def validate_password_strength(password: str, service_name: str = "default") -> Dict[str, Any]:
    """
    验证密码强度（便捷函数）
    
    Args:
        password: 密码
        service_name: 服务名称
        
    Returns:
        Dict[str, Any]: 验证结果
    """
    manager = get_password_manager(service_name)
    return manager.validate_password_strength(password)


def generate_password(length: int = 12, include_symbols: bool = True, service_name: str = "default") -> str:
    """
    生成安全密码（便捷函数）
    
    Args:
        length: 密码长度
        include_symbols: 是否包含特殊字符
        service_name: 服务名称
        
    Returns:
        str: 生成的密码
    """
    manager = get_password_manager(service_name)
    return manager.generate_password(length, include_symbols)


def generate_reset_token(service_name: str = "default") -> str:
    """
    生成密码重置令牌（便捷函数）
    
    Args:
        service_name: 服务名称
        
    Returns:
        str: 重置令牌
    """
    manager = get_password_manager(service_name)
    return manager.generate_reset_token()


# =============================================================================
# 导出的主要函数
# =============================================================================

__all__ = [
    "UnifiedPasswordManager",
    "get_password_manager",
    "hash_password",
    "verify_password",
    "validate_password_strength",
    "generate_password",
    "generate_reset_token"
]
